# -*- coding: utf-8 -*-

#################
#    Imports    #
#################
import sys
import os
import codecs
import collections
import csv

import xlrd
import matplotlib
matplotlib.use("QT4Agg")
import matplotlib.pyplot as plt
import numpy as np
import cProfile
import pstats
import time

from DatabaseMngr import Escola
from UnicodeCSV import UnicodeWriter


class DataMngr(object):
    """
    Manage, parse and dump data
    """

    # Simple and raw data
    data = []

    # Parsed data grouped by instituições
    dataparsed = {}

    # Stats about totals
    stats = {}

    # Districts and their associations
    districts = {
        u'Lisboa': [u'Lisboa', u'Estoril', u'Infante D. Henrique'],
        u'Porto': [u'Porto'],
        u'Setúbal': [u'Setúbal'],
        u'Braga': [u'Minho', u'Cávado e do Ave'],
        u'Aveiro': [u'Aveiro'],
        u'Leiria': [u'Leiria'],
        u'Santarém': [u'Santarém', u'Tomar'],
        u'Faro': [u'Algarve'],
        u'Coimbra': [u'Coimbra'],
        u'Viseu': [u'Viseu'],
        u'R.A Madeira': [u'Madeira'],
        u'R.A Açores': [u'Açores'],
        u'Viana do Castelo': [u'Viana do Castelo'],
        u'Vila Real': [u'Trás-os-Montes e Alto Douro'],
        u'Castelo Branco': [u'Castelo Branco', u'Beira Interior'],
        u'Évora': [u'Évora'],
        u'Guarda': [u'Guarda'],
        u'Beja': [u'Beja'],
        u'Bragança': [u'Bragança'],
        u'Portalegre': [u'Portalegre'],
    }

    def __init__(self):
        """
        Construtor
        """
        pass

    def load_from_xls(self, filename):
        """
        Loads class from a xls file
        filename -- Full file path to load xls
        """

        if not isinstance(filename, str):
            raise ValueError('filename is not a string!')

        if not os.path.isfile(filename):
            raise IOError('filename doesn\'t exists')

        try:
            # Open sheet to read
            wb = xlrd.open_workbook(filename)
            wb.sheet_names()
            sh = wb.sheet_by_index(0)

            # Loop data and add to a list
            for rownum in range(3, sh.nrows - 2):
                self.data.append(sh.row_values(rownum))

            # Parse data
            self.reparse_data()
        except IOError:
            print "Not possible to open file: ", sys.exc_info()
        except:
            print "Not possible to open/parse XLS file: ", sys.exc_info()
        pass

    def add(self, obj):
        """
        Add a object to data
        obj -- Object to add
        """
        self.data.append(obj)

    def get(self, index):
        """
        Get a object given a index
        index -- A valid index
        """
        return self.data[index]

    def get_district(self, text):
        """
        Get the district from a string
        text -- String to search a district in
        """
        for k, v in self.districts.iteritems():
            for x in v:
                if x in text:
                    return k
        return 'Desconhecido'

    def reparse_data(self):
        """
        Re parse data and arrange information
        """

        self.stats = {'instituicoes': {'cursos': 0,
                                       'totalcolocados': 0,
                                       'totalvagasiniciais': 0,
                                       'totalvagasrestantes': 0},
                      'districts': {}}
        self.dataparsed = dict()

        # Loop data
        for row in self.data:
            # Get instituição only to group
            instituicao_nome = row[Escola.IDX_INSTITUICAONOME].split(' - ', 1)

            # Create instituição if not exists and set his defaults
            instituicao = self.dataparsed.setdefault(instituicao_nome[0],
                                                     {'nome': row[Escola.IDX_INSTITUICAONOME],
                                                      'distrito': self.get_district(row[Escola.IDX_INSTITUICAONOME]),
                                                      'totalcolocados': 0,
                                                      'totalvagasiniciais': 0,
                                                      'totalvagasrestantes': 0,
                                                      'cursos': {}})

            # Sum results to current instituição
            instituicao['totalcolocados'] += int(row[Escola.IDX_COLOCADOS])
            instituicao['totalvagasiniciais'] += int(row[Escola.IDX_VAGAS])
            instituicao['totalvagasrestantes'] += int(row[Escola.IDX_VAGASRESTANTES])

            # Sum results to total stats
            self.stats['instituicoes']['totalcolocados'] += int(row[Escola.IDX_COLOCADOS])
            self.stats['instituicoes']['totalvagasiniciais'] += int(row[Escola.IDX_VAGAS])
            self.stats['instituicoes']['totalvagasrestantes'] += int(row[Escola.IDX_VAGASRESTANTES])

            # Create and append cursos dictionary if not exists and set his defaults
            instituicao['cursos'].setdefault(row[Escola.IDX_CODCURSO], {'nome': row[Escola.IDX_CURSO_NOME],
                                                                        'grau': row[Escola.IDX_GRAU],
                                                                        'colocados': int(row[Escola.IDX_COLOCADOS]),
                                                                        'vagasiniciais': int(row[Escola.IDX_VAGAS]),
                                                                        'vagasrestantes': int(
                                                                            row[Escola.IDX_VAGASRESTANTES]),
                                                                        'notaultimocolocado': row[
                                                                            Escola.IDX_NOTACANDIDATURA]})

            # Create distritos if not exists and set his defaults
            district = self.stats['districts'].setdefault(instituicao['distrito'], {'cursos': 0,
                                                                                    'totalcolocados': 0,
                                                                                    'totalvagasiniciais': 0,
                                                                                    'totalvagasrestantes': 0})
            # Sum results to current district
            district['cursos'] += 1
            district['totalcolocados'] += int(row[Escola.IDX_COLOCADOS])
            district['totalvagasiniciais'] += int(row[Escola.IDX_VAGAS])
            district['totalvagasrestantes'] += int(row[Escola.IDX_VAGASRESTANTES])

        # Sort our dictionaries
        self.dataparsed = collections.OrderedDict(sorted(self.dataparsed.items()))
        self.stats['districts'] = collections.OrderedDict(sorted(self.stats['districts'].items()))

    def dump(self, print_to_file=None, print_deep=True):
        """
        Dump parsed data to console and/or file
        print_to_file -- Filename to dump results to, None to print to console only
        print_deep -- True if we want to dump deep arrays ( more than 1 key in deep )
        """

        if not print_to_file is None and not isinstance(print_to_file, str):
            raise ValueError('filename is not a string!')

        file = None
        if print_to_file:
            try:
                file = codecs.open(print_to_file, 'wb', "utf-8")            # Open file for write in utf-8 mode
            except:
                print "Unable to open file for write: " + str(sys.exc_info())

        msg = u'#######################{0}' \
              u'#        Totais       #{0}' \
              u'#######################{0}' \
              u'Total Colocados: {1}{0}' \
              u'Total Vagas iniciais: {2}{0}' \
              u'Total Vagas Restantes: {3}{0}{0}' \
              u'#######################{0}' \
              u'#     Instituições    #{0}' \
              u'#######################'.format(os.linesep,
                                                self.stats['instituicoes']['totalcolocados'],
                                                self.stats['instituicoes']['totalvagasiniciais'],
                                                self.stats['instituicoes']['totalvagasrestantes'])
        print msg
        if file:
            file.write(msg + os.linesep)

        for key, value in self.dataparsed.iteritems():
            msg = u"{1}: {2} [{3}]{0}" \
                  "  |- Total Colocados: {4}{0}" \
                  "  |- Total Vagas Iniciciais: {5}{0}" \
                  "  |- Total Vagas Sobra: {6}{0}" \
                  "  |- Cursos ({7})".format(os.linesep,
                                             key,
                                             value['nome'],
                                             value['distrito'],
                                             value['totalcolocados'],
                                             value['totalvagasiniciais'],
                                             value['totalvagasrestantes'],
                                             len(value['cursos']))

            print msg
            if file:
                file.write(msg + os.linesep)

            if print_deep:
                for cursokey, cursovalue in value['cursos'].iteritems():
                    msg = u"    |- {1}: {2}{0}" \
                          "      |- Grau: {3}{0}" \
                          "      |- Colocados: {4}{0}" \
                          "      |- Vagas Iniciciais: {5}{0}" \
                          "      |- Vagas Sobra: {6}{0}" \
                          "      |- Nota Ultimo Colocado: {7}".format(os.linesep, cursokey, cursovalue['nome'],
                                                                      cursovalue['grau'], cursovalue['colocados'],
                                                                      cursovalue['vagasiniciais'],
                                                                      cursovalue['vagasrestantes'],
                                                                      cursovalue['notaultimocolocado'])
                    print msg
                    if file:
                        file.write(msg + os.linesep)

        msg = '{0}#######################{0}' \
              '#      Distritos      #{0}' \
              '#######################'.format(os.linesep)
        print msg
        if file:
            file.write(msg + os.linesep)

        for distritokey, distritovalue in self.stats['districts'].iteritems():
            msg = u'{1}{0}' \
                  u'  |- Cursos: {2}{0}' \
                  u'  |- Total Colocados: {3}{0}' \
                  u'  |- Total Vagas iniciais: {4}{0}' \
                  u'  |- Total Vagas Restantes: {5}'.format(os.linesep,
                                                            distritokey,
                                                            distritovalue['cursos'],
                                                            distritovalue['totalcolocados'],
                                                            distritovalue['totalvagasiniciais'],
                                                            distritovalue['totalvagasrestantes'],
                                                            )

            print msg
            if file:
                file.write(msg + os.linesep)

        # And finally close the file if is open
        if file:
            file.close()

    def dump_csv(self, filename):
        """
        Dump parsed data to some csv's
        filename -- Filename without extension to dump results as csv
        """

        if not isinstance(filename, str):
            raise ValueError('filename is not a string!')

        try:
            realfilename, ext = os.path.splitext(filename)  # Split extension to remove it
            with open("{0}_instituicao.csv".format(realfilename), "wb") as fileInstituicao:
                csvInstituicao = UnicodeWriter(fileInstituicao, quoting = csv.QUOTE_ALL)

                for key, value in self.dataparsed.iteritems():
                    csvInstituicao.writerow([key,
                                             str(value['totalcolocados']), # o número de alunos colocados por instituição;
                                             str(round(float(value['totalcolocados']) * 100 / float(self.stats['instituicoes']['totalcolocados']), 4)),  # a percentagem de alunos colocados por instituição em relação a todos os alunos colocados;
                                             str(value['totalvagasrestantes'])])  # o número de vagas por colocar por instituição;

            with open("{0}_distrito.csv".format(realfilename), "wb") as fileDistrito:
                cvsDistrito = UnicodeWriter(fileDistrito, quoting=csv.QUOTE_ALL)

                for key, value in self.stats['districts'].iteritems():
                    cvsDistrito.writerow([key,
                                         str(value['totalcolocados']),  # o número de alunos colocados por distrito;
                                         str(round(float(value['totalcolocados']) * 1000 / self.stats['instituicoes']['totalcolocados'], 4)),  # a permilagem de alunos colocados por distrito;
                                         str(value['totalvagasrestantes'])])  # o número de vagas por colocar por distrito.
        except:
            print "Error write csv stats: ",  sys.exc_info()

    def plot_colocados_por_instituicao(self, args=None):
        """
        Plot número de alunos colocados por instituição
        """

        # If args is None call the same function in a separate process
        #if args is None:
            #p = multiprocessing.Process(target=self.plot_colocados_por_instituicao, args=(True,))
            #p.start()
            #p = threading.Thread(target=self.plot_colocados_por_instituicao, args=(True,))
            #p.daemon = False
            #p.start()
            #print self.p.getName()
            #return None


        graphdata = {'nome': [], 'totalcolocados': []}

        for key, value in self.dataparsed.iteritems():
            graphdata['nome'].append(key)
            graphdata['totalcolocados'].append(value['totalcolocados'])

        y_pos = np.arange(len(graphdata['totalcolocados']))

        plt.clf()
        plt.close()
        plt.barh(y_pos + 1, graphdata['totalcolocados'], align='center', alpha=0.4)
        plt.yticks(y_pos + 1, graphdata['nome'])
        plt.xlabel('Colocados')
        plt.title(u'Número de alunos colocados por instituição')
        plt.ion()
        plt.show()

    def plot_colocados_por_total(self, args=None):
        """
        Plot percentagem de alunos colocados por instituição em relação a todos os alunos colocados
        """

        # If args is None call the same function in a separate thread
        """if args is None:
            #p = multiprocessing.Process(target=self.plot_colocados_por_total, args=(True,))
            #p.start()
            p = threading.Thread(target=self.plot_colocados_por_total, args=(True,))
            p.daemon = True
            p.start()
            return None"""

        graphdata = {'nome': [], 'colocados_por_total': []}

        for key, value in self.dataparsed.iteritems():
            graphdata['nome'].append(key)
            graphdata['colocados_por_total'].append(float(value['totalcolocados']) * 100 / float(self.stats['instituicoes']['totalcolocados']))

        y_pos = np.arange(len(graphdata['colocados_por_total']))

        plt.clf()
        plt.close()
        plt.barh(y_pos + 1, graphdata['colocados_por_total'], align='center', alpha=0.4)
        plt.yticks(y_pos + 1, graphdata['nome'])
        plt.xlabel(u'Percentagem (%)')
        plt.title(u'Percentagem de alunos colocados por instituição em relação a todos os alunos colocados')
        plt.ion()
        plt.show()

    def plot_vagas_por_colocar_por_instiuicao(self, args=None):
        """
        Plot alunos colocados por instituição 
        """

        # If args is None call the same function in a separate thread
        """if args is None:
            p = multiprocessing.Process(target=self.plot_vagas_por_colocar_por_instiuicao, args=(True,))
            p.start()
            return None"""

        graphdata = {'nome': [], 'vagas_por_instituicao': [], 'vagas_restantes_por_instituicao' : []}

        for key, value in self.dataparsed.iteritems():
            graphdata['nome'].append(key)
            graphdata['vagas_por_instituicao'].append(float(value['totalvagasiniciais']))
            graphdata['vagas_restantes_por_instituicao'].append(float(value['totalvagasrestantes']))

        y_pos = np.arange(len(graphdata['vagas_por_instituicao']))

        plt.clf()
        plt.close()
        vagas_i = plt.barh(y_pos + 1, graphdata['vagas_por_instituicao'],alpha=0.2, label = "Vagas Iniciais")
        vagas_s = plt.barh(y_pos + 1, graphdata['vagas_restantes_por_instituicao'], alpha=0.4, label = "Vagas Restantes")
        plt.yticks(y_pos + 1.5, graphdata['nome'])

        plt.xlabel(u'Nº Vagas iniciais / Nº de vagas')
        plt.title(u'Vagas Iniciais / Restantes por instituição')
        plt.legend(loc = 4)

        #Show numbers over bars
        """
        def autolabel(rects):
    
            for rect in rects:
                height = rect.get_height()
                plt.text(rect.get_x()+rect.get_width()/2., 1.05*height, '%d'%int(height), ha='center', va='bottom', rotation = 90)

        autolabel(vagas_i)
        autolabel(vagas_s)
        """
        plt.ion()
        plt.show()

    def plot_percentagem_colocados_por_instiuicao(self, args=None):
        """
        Plot percentagem de alunos colocados por instituição em relação a todos os alunos colocados
        """

        # If args is None call the same function in a separate thread
        """if args is None:
            p = multiprocessing.Process(target=self.plot_percentagem_colocados_por_instiuicao, args=(True,))
            p.start()
            return None"""

        graphdata = {'nome': [], 'colocados_por_total': []}

        for key, value in self.dataparsed.iteritems():
            graphdata['nome'].append(key)
            graphdata['colocados_por_total'].append(float(value['totalcolocados']) * 100 / float(self.stats['instituicoes']['totalcolocados']))

        y_pos = np.arange(len(graphdata['colocados_por_total']))

        plt.clf()
        plt.close()
        plt.barh(y_pos + 1, graphdata['colocados_por_total'], align='center', alpha=0.4)
        plt.yticks(y_pos + 1, graphdata['nome'])
        plt.xlabel(u'Percentagem (%)')
        plt.title(u'Percentagem de alunos colocados por instituição em relação a todos os alunos colocados')
        plt.ion()
        plt.show()

    def plot_alunos_colocados_por_distrito(self, args=None):
        """
        Plot Alunos colocados por Distrito 
        """

        # If args is None call the same function in a separate thread
        """if args is None:
            p = multiprocessing.Process(target=self.plot_alunos_colocados_por_distrito, args=(True,))
            p.start()
            return None"""

        graphdata = {'nome': [], 'colocados_por_distrito': [], 'vagas_restantes_por_instituicao': [] }

        for key, value in self.stats['districts'].iteritems():
            graphdata['nome'].append(key)
            graphdata['colocados_por_distrito'].append(float(value['totalcolocados']))
            graphdata['vagas_restantes_por_instituicao'].append(float(value['totalvagasrestantes']))

        y_pos = np.arange(len(graphdata['colocados_por_distrito']))

        plt.clf()
        plt.close()
        colocados = plt.barh(y_pos, graphdata['colocados_por_distrito'], align='center', alpha=0.2, label = "Colocados")
        vagas_s = plt.barh(y_pos, graphdata['vagas_restantes_por_instituicao'], align = 'center',alpha=0.4, label = "Vagas Restantes")
        plt.yticks(y_pos, graphdata['nome'])

        plt.xlabel(u'Colocados / Vagas Restantes')
        plt.title(u'Alunos colocados por Distrito')

        plt.legend(loc = 1)
        plt.ion()
        plt.show()

    def plot_permilagem_de_alunos_colocados_por_distrito(self, args=None):
        """
        Plot permilagem de alunos colocados por distrito
        """

        """# If args is None call the same function in a separate thread
        if args is None:
            p = multiprocessing.Process(target=self.plot_permilagem_de_alunos_colocados_por_distrito, args=(True,))
            p.start()
            return None"""

        graphdata = {'nome': [], 'permilagem': []}

        for key, value in self.dataparsed.iteritems():
            graphdata['nome'].append(key)
            graphdata['permilagem'].append(float(value['totalcolocados']) * 100 / float(self.stats['instituicoes']['totalcolocados']))

        y_pos = np.arange(len(graphdata['permilagem']))

        plt.clf()
        plt.close()
        plt.barh(y_pos + 1, graphdata['permilagem'], align='center', alpha=0.4)
        plt.yticks(y_pos + 1, graphdata['nome'])
        plt.xlabel(u'Permilagem (‰)')
        plt.title(u'Permilagem de alunos colocados por distrito')
        plt.ion()
        plt.show()

    def plot_vagas_por_colocar_por_distrito(self, args=None):
        """
        Plot vagas por colocar por distrito
        """

        # If args is None call the same function in a separate thread
        """if args is None:
            p = multiprocessing.Process(target=self.plot_vagas_por_colocar_por_distrito, args=(True,))
            p.start()
            return None"""

        graphdata = {'nome': [], 'vagas_por_distrito': [], 'vagas_restantes_por_distrito' : []}

        for key, value in self.stats['districts'].iteritems():
            graphdata['nome'].append(key)
            graphdata['vagas_por_distrito'].append(float(value['totalvagasiniciais']))
            graphdata['vagas_restantes_por_distrito'].append(float(value['totalvagasrestantes']))

        y_pos = np.arange(len(graphdata['vagas_por_distrito']))

        plt.clf()
        plt.close()
        vagas_i = plt.barh(y_pos + 1, graphdata['vagas_por_distrito'],alpha=0.2, label = "Vagas Iniciais")
        vagas_s = plt.barh(y_pos + 1, graphdata['vagas_restantes_por_distrito'], alpha=0.4, label = "Vagas Restantes")
        plt.yticks(y_pos + 1.5, graphdata['nome'])

        plt.xlabel(u'Nº Vagas iniciais / Nº de vagas')
        plt.title(u'Vagas Iniciais / Restantes por distrito')
        plt.legend(loc = 4)

        plt.ion()
        plt.show()

    def plot_all(self):
        """
        Plot all available graphs
        """

        self.plot_colocados_por_instituicao()
        self.plot_percentagem_colocados_por_instiuicao()
        self.plot_vagas_por_colocar_por_instiuicao()
        self.plot_alunos_colocados_por_distrito()
        self.plot_permilagem_de_alunos_colocados_por_distrito()
        self.plot_vagas_por_colocar_por_distrito()